package cc.avatar.repository;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import cc.avatar.model.AccountingEntry;
import cc.avatar.model.BusinessException;
import cc.avatar.model.Carriage;
import cc.avatar.model.CarriageItem;
import cc.avatar.model.Customer;
import cc.avatar.model.Party;
import cc.avatar.model.Product;
import cc.avatar.model.Sale;
import cc.avatar.model.SaleItem;
import cc.avatar.model.SaleState;
import cc.avatar.model.dao.AccountingEntryDao;
import cc.avatar.model.dao.CarriageDao;
import cc.avatar.model.dao.CustomerDao;
import cc.avatar.model.dao.InventoryDao;
import cc.avatar.model.dao.LocationDao;
import cc.avatar.model.dao.PartyDao;
import cc.avatar.model.dao.ProductDao;
import cc.avatar.model.dao.SaleDao;
import cc.avatar.model.dao.SaleItemDao;
import cc.avatar.model.dto.CarriageDto;
import cc.avatar.model.dto.ProductSaleDto;
import cc.avatar.model.dto.QueryDto;
import cc.avatar.model.dto.SaleDto;
import cc.avatar.model.inventory.InventoryChange;
import cc.avatar.model.inventory.InventoryChangeEvent;
import cc.avatar.model.inventory.InventoryChangeType;
import cc.avatar.util.DateUtil;

import com.urlshow.pinyin.PinYin;

public class SaleRepository implements ApplicationContextAware {
	
	public Sale getSaleById(Long id) {
		return this.saleDao.getById(id);
	}

	public void saveCustomer(final Customer customer) {
		this.customerDao.save(customer);
	}

	@SuppressWarnings("unchecked")
	public List<SaleItem> findSales(QueryDto query){
		StringBuilder hql =  new StringBuilder("from SaleItem si where si.sale in( select s ");
		hql.append(query.getCarrier() == null ?"from Sale s":"from Sale s, Carriage c");
		hql.append(" where 1=1");
		    
		List params = new ArrayList();
		if (!StringUtils.isEmpty(query.getTrackingNumber())){
			hql.append(" and s.trackingNumber like ?");
			params.add("%" + query.getTrackingNumber() + "%");
		}
		if (!StringUtils.isEmpty(query.getCustomerName())){
			hql.append(" and s.customer.name like ?");
			params.add("%" + query.getCustomerName() + "%");
		}
		
		if (!StringUtils.isEmpty(query.getStartDate())){
			DateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd");
			Date start;
			try {
				start = df.parse(query.getStartDate());
				hql.append(" and s.createdTime between ? and ?");				
				params.add(start);
				if (StringUtils.isEmpty(query.getEndDate()))				
					params.add(DateUtil.getNextDay(df.parse(query.getStartDate())));
				else
					params.add(df.parse(query.getEndDate()));
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
		if (query.getSalesman() != null){
			hql.append( "and s.salesPerson.id = ? ");
			params.add(query.getSalesman());
		}
		if (query.getCarrier() != null){
			hql.append(" and c in elements(s.carriages) and c.carrier.id = ?");
			params.add(query.getCarrier());
		}
				
		if (query.getStates()!=null && query.getStates().length > 0){
			hql.append(" and s.state in (");
			boolean first = true;
			for (SaleState state : query.getStates()){
				hql.append(!first?",?":"?");
				first = false;
				params.add(state);
			}
			hql.append(")");			
		}
		hql.append(")");
		hql.append(" order by si.sale.createdTime desc");
		return saleDao.getHibernateTemplate().find(hql.toString(), params.toArray()); 		
	}
	
	public void updateSaleItemPrice(Long id, int planAmount, int price){
		SaleItem si = saleItemDao.getById(id);
		si.setPrice(price);
		si.setContractAmount(planAmount);
		saleItemDao.save(si);
	}
	
	private final static String HQL_GET_TOTAL_SUM = "select sum(items.amount*items.price) from Sale s" +
	       " left join s.items as items where s.state = ? and s.closeTime between ? and ? and items.product.id = ?";
	private final static String HQL_GET_TOTAL_AMOUNT = "select sum(items.amount) from Sale s" +
	        " left join s.items as items where s.state = ? and s.closeTime between ? and ? and items.product.id = ?";
	private final static String HQL_GET_PRODUCTS_IN_SALE = "select distinct items.product from Sale s left join s.items as items " +
			" where s.state = ? and s.closeTime between ? and ?";
	
	@SuppressWarnings("unchecked")
	public List<CarriageItem> findCarriageItems(Long carrierId, String from, String to){
		DateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd");
		Date start, end;
		try {			
			start = df.parse(from);
			end = df.parse(to);
		} catch (ParseException e) {
			throw new BusinessException("Q01");
		}	
		String hql = "select ci from Carriage c left join c.items ci where c.carrier.id = ? and c.whenOccurred between ? and ? ";
		return saleDao.getHibernateTemplate().find(hql, new Object[]{carrierId, start, end});
	}
	
	@SuppressWarnings("unchecked")
	public List<ProductSaleDto> getSaleSummaryReport(String from, String to){ 
		DateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd");
		Date start, end;
		try {			
			start = df.parse(from);
			end = df.parse(to);
		} catch (ParseException e) {
			throw new BusinessException("Q01");
		}
			
		Object[] params = new Object[] {SaleState.close, start, end}; 
		List<Product> products = (List<Product>)saleDao.getHibernateTemplate().find(HQL_GET_PRODUCTS_IN_SALE,
				params); 
		List<ProductSaleDto> results = new ArrayList<ProductSaleDto>();
		for (Product p : products){
			ProductSaleDto psd = new ProductSaleDto();
			psd.setProductName(p.getName());

            List amount = saleDao.getHibernateTemplate().find(HQL_GET_TOTAL_AMOUNT,
					 new Object[] {SaleState.close, start, end, p.getId()}); 
			List sum = saleDao.getHibernateTemplate().find(HQL_GET_TOTAL_SUM,
					 new Object[] {SaleState.close, start, end, p.getId()}); 
			
			psd.setAmount((Long)amount.get(0));
			psd.setSum((Long)sum.get(0));
			psd.setAvaragePrice(psd.getSum()/psd.getAmount());
			results.add(psd);
		}	
		return results;
	}
	
	public void removeSaleItem(Long id){		
	    SaleItem si = this.saleItemDao.getById(id);
	    assert(si.getAmount() == 0);
	    
	    Sale sale = si.getSale();
	    sale.getItems().remove(si);
	    
	    saleDao.save(sale);
	    saleItemDao.deleteById(id);
	}
	
	public void addSaleItem(Long saleId, Long productId, int planAmount){
		Sale sale = saleDao.getById(saleId);
		assert(sale.getState() == SaleState.draft || sale.getState() == SaleState.in_transit);
		
		Product p = productDao.getById(productId);
		SaleItem si = new SaleItem();
		si.setProduct(p);
		si.setSale(sale);
		si.setContractAmount(planAmount);
		sale.getItems().add(si);
		saleDao.save(sale);
	}
	
	public void addSale(SaleDto sale){
		sale.validate();
		Sale newSale = new Sale();
		Customer c = null;
		if (sale.getCustomer().getId() != null){
			//reload
			c = customerDao.getById(sale.getCustomer().getId());
			c.setName(sale.getCustomer().getName());
			c.setFreightPrice(sale.getCustomer().getFreightPrice());
			c.setPhoneNumber(sale.getCustomer().getPhoneNumber());
		} else
            c = sale.getCustomer();
		c.setPinyin(PinYin.getPinYin(c.getName()));
		newSale.setCustomer(c);
		newSale.setSalesPerson(partyDao.getById(sale.getSalerId()));
		newSale.setCreatedTime(new Date());
		//add items
		for (int i=0; i<sale.getProductIds().length ;i++){
			Long pid = sale.getProductIds()[i];
			SaleItem si = new SaleItem();
			si.setProduct(this.productDao.getById(pid));
			si.setSale(newSale);
			si.setContractAmount(sale.getAmounts()[i]);
			newSale.getItems().add(si);
		}
		this.saleDao.save(newSale);
	}
	
	@SuppressWarnings("unchecked")
	public List<Customer> getCustomersByPinyin(String pinyin){
		String  hql =  "from Customer c where c.pinyin like ?";
		return saleDao.getHibernateTemplate().find(hql, "%" + pinyin + "%"); 
	}

	private Carriage parseToCarriage(String customerName, float freightPrice, CarriageDto carr) {
		Carriage carriage = new Carriage();
		//firstCarri.setSale(s);
		carriage.setWhenOccurred(new Date());
		carriage.setCarrier(partyDao.getById(carr.getCarrierId()));
		carriage.setReturned(carr.isReturned());
		for (int i=0; i< carr.getProductIds().length;i++){
			CarriageItem ci = new CarriageItem();
			//复制运输价格
			ci.setFreightPrice(freightPrice);
			ci.setProduct(productDao.getById(carr.getProductIds()[i]));
			ci.setAmount(carr.getAmounts()[i]);
			ci.setLocation(locationDao.getById(carr.getLocationIds()[i]));
			carriage.addItem(customerName, ci);
		}
		return carriage;
	}
	
	@SuppressWarnings("unchecked")
	public List<CarriageItem> getCarriageItemsBySaleId(Long saleId){
		String  hql =  "select items from Carriage c left join c.items as items where c.sale.id = ? order by items.id";
		return saleDao.getHibernateTemplate().find(hql, saleId); 
	}
	
	public void addCarriage(Long saleId, CarriageDto c) {
		Sale sale = this.getSaleById(saleId);
		Carriage carriage = parseToCarriage(sale.getCustomer().getName(), sale.getCustomer().getFreightPrice(), c);
		//first carriage
		if (sale.getState() == SaleState.draft){
			//update time
			sale.setCreatedTime(new Date());			
			sale.setTrackingNumber(counter.createSaleTrackingNumber());
			sale.setState( SaleState.in_transit );
			this.saleDao.save(sale);
		}
		sale.addCarriage(carriage);
		if (sale.getCustomer().getFreightPrice()>0){
			accountingEntryDao.save(carriage.process());
			carriage.setCarriageForward(false);
		}
		this.carriageDao.save(carriage);
		this.saleDao.save(sale);

		// add inventory logs
		for (CarriageItem item : carriage.getItems()) {
			InventoryChange ii = new InventoryChange();
			ii.setProduct(item.getProduct());
			ii.setType(InventoryChangeType.sale_out);
			ii.setLocation(item.getLocation());
			ii.setQuantity(-item.getAmount());
			ii.setComments(carriage.getSale().getCustomer().getName() + " " + carriage.getSale().getTrackingNumber());
			ii.setCreatedTime(carriage.getWhenOccurred());
			inventoryDao.save(ii);
		}
		applicationContext.publishEvent(new InventoryChangeEvent(carriage));
	}
	public void payfull(Long saleId, Long payerId, int commisionPrice, float freightPrice) {
		Sale sale = this.getSaleById(saleId);
		assert (sale.getState() != SaleState.close);

		sale.setEffectivePayment(sale.getReceivablePayment());
		sale.setState(SaleState.close);
		sale.setCloseTime(new Date());
		sale.setCommisionPrice(commisionPrice);
		if (sale.getCustomer().getFreightPrice() == 0){
			sale.getCustomer().setFreightPrice(freightPrice);
		}
		this.saleDao.save(sale);
		
		// add carriage accounting entries
		for (Carriage c: sale.getCarriages()){
			if (c.isCarriageForward()){
		        c.setCarriageForward(false);
		        carriageDao.save(c);
				accountingEntryDao.save(c.process());
			}
		}
		
		//add salesman accounting entry
		AccountingEntry entry = sale.process();
		//if (entry.getAmount() != 0)
		    accountingEntryDao.save(entry);
		
		//add payer accounting entry
		Party payer = partyDao.getById(payerId);
		AccountingEntry payerEntry = new AccountingEntry(payer, sale.getEffectivePayment(), 
				entry.getDate(), entry.getAtt1(), entry.getAtt2(), "", "");
		accountingEntryDao.save(payerEntry);
	}
	
	@SuppressWarnings("unchecked")
	public int addSelfSaleEntry(){
		String hql = "from Sale where state = ? and commisionPrice = 0";		
		List<Sale> sales = (List<Sale>)saleDao.getHibernateTemplate().find(hql, SaleState.close); 
		hql = "from AccountingEntry where party = ? and att1 = ?";
		int hit = 0;
		for (Sale sale : sales){
			List results = accountingEntryDao.getHibernateTemplate().find(hql,
					new Object[]{sale.getSalesPerson(), sale.getTrackingNumber()}); 
			if (results.isEmpty()){
				AccountingEntry entry = sale.process();
				accountingEntryDao.save(entry);
				hit ++;
			}
		}

		return hit;
	}

	public void pay(Long saleId, Long payerId, int money) {
		Sale sale = this.getSaleById(saleId);
		assert (sale.getState() != SaleState.close);
		int leftPayment = sale.getReceivablePayment() - sale.getEffectivePayment();

		if (money > leftPayment) {
			throw new BusinessException("S03", String.valueOf(leftPayment));
		}
        if (sale.getState() == SaleState.in_transit)
        	sale.setState(SaleState.prepare_for_payment);
		sale.setEffectivePayment(sale.getEffectivePayment() + money);
		this.saleDao.save(sale);
		
		Party payer = partyDao.getById(payerId);
		AccountingEntry payerEntry = new AccountingEntry(payer, money, 
				new Date(), sale.getAtt1(), sale.getAtt2(), "", "");
		accountingEntryDao.save(payerEntry);
	}

	private SaleDao saleDao;
	private CustomerDao customerDao;
	private ProductDao productDao;
	private AccountingEntryDao accountingEntryDao;
	private InventoryDao inventoryDao;
	private CarriageDao carriageDao;
	private SaleItemDao saleItemDao;
	private PartyDao partyDao;
	private TrackingNumberCounter counter;
	private LocationDao locationDao;
	private ApplicationContext applicationContext;

	/**
	 * @param applicationContext
	 *            the applicationContext to set
	 */
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.applicationContext = applicationContext;
	}

	// IOC setter
	public void setCarriageDao(CarriageDao carriageDao) {
		this.carriageDao = carriageDao;
	}

	public void setCounter(TrackingNumberCounter counter) {
		this.counter = counter;
	}

	public void setSaleItemDao(SaleItemDao saleItemDao) {
		this.saleItemDao = saleItemDao;
	}

	public void setCustomerDao(CustomerDao customerDao) {
		this.customerDao = customerDao;
	}

	public void setSaleDao(SaleDao saleDao) {
		this.saleDao = saleDao;
	}

	public void setInventoryDao(InventoryDao inventoryDao) {
		this.inventoryDao = inventoryDao;
	}

	public void setAccountingEntryDao(AccountingEntryDao accountingDao) {
		this.accountingEntryDao = accountingDao;
	}

	public void setProductDao(ProductDao productDao) {
		this.productDao = productDao;
	}

	public void setPartyDao(PartyDao partyDao) {
		this.partyDao = partyDao;
	}

	public void setLocationDao(LocationDao locationDao) {
		this.locationDao = locationDao;
	}
}
